// $Rev: 130 $

#include <sys/socket.h>
#include <string>
#include "incoming_connection.h"
#include "util.h"
#include "constants.h"

using namespace std;

IncomingConnection::IncomingConnection(Server *server, int socket,
                                       pollfd *poll_info) :
	Connection(socket, poll_info), server(server),
	state(AWAITING_MESSAGE) {
	filter_events(POLLIN);
	log("New client.");
}

static const int BUFSIZE = 1024;

void IncomingConnection::process_events() {
	static char buf[BUFSIZE];
	if (state == AWAITING_MESSAGE && can_receive()) {
		ssize_t numbytes = receive(buf, BUFSIZE);
		if (numbytes < 0) {
			error("Error reading from socket");
		} else if (numbytes == 0) {
			log("Connection closed by client.");
			kill_connection();
		} else {
			buffer += string(buf, numbytes);
			size_t pos;
			while ((pos = buffer.find(separator)) != string::npos) {
				process_command(buffer.substr(0, pos));
				buffer.erase(0, pos + separator.length());
			}
		}
	}
	if (state == SENDING_REPLY && can_send()) {
		ssize_t numbytes = send(reply.c_str(), reply.length());
		if (numbytes < 0) {
			error("Error writing to socket");
		} else {
			reply.erase(0, numbytes);
			if (reply.empty()) {
				change_state(AWAITING_MESSAGE);
			}
		}
	}
}

void IncomingConnection::process_command(const string &command) {
	vector<string> comm = parse(command);
	if (comm.size() == 0) {
		log("Command not recognized: " + command);
		reply = get_error_message(200);
		change_state(SENDING_REPLY);
	} else if (command.find('\n') != string::npos) {
		log("\\n detected: " + command);
		reply = get_error_message(100);
		change_state(SENDING_REPLY);
	} else if (comm[0] == "hello") {
		hello(comm);
	} else if (comm[0] == "get_file_listing") {
		get_file_listing(comm);
	} else if (comm[0] == "get_metadata") {
		get_metadata(comm);
	} else if (comm[0] == "get_sources") {
		get_sources(comm);
	} else if (comm[0] == "new_fragment") {
		new_fragment(comm);
	} else if (comm[0] == "quit") {
		log("Closing connection with client.");
		kill_connection();
	} else {
		log("Command not recognized: " + command);
		reply = get_error_message(200);
		change_state(SENDING_REPLY);
	}
	return;
}

void IncomingConnection::hello(const vector<string> &comm) {
	if (comm.size() != 2 || !is_number(comm[1])) {
		reply = get_error_message(201);
	} else {
		if (get_address().find(':') == string::npos) {
			set_address(get_address() + ':' + comm[1]);
		} else {
			set_address(get_address().substr(0, get_address().find(':')) +
			            ':' + comm[1]);
		}
		reply = get_error_message(0);
	}
	change_state(SENDING_REPLY);
}

void IncomingConnection::get_file_listing(const vector<string> &comm) {
	if (comm.size() != 2) {
		reply = get_error_message(201);
	} else if (!server) {
		reply = get_error_message(199);
	} else {
		reply = get_error_message(0);
		const vector<const File *> &files = server->get_index().get_files();
		for (size_t i = 0; i < files.size(); i++) {
			if (files[i]->get_name().find(comm[1]) != string::npos) {
				reply += files[i]->get_md5() + ' ' +
				         files[i]->get_name() + separator;
			}
		}
		reply += separator;
	}
	change_state(SENDING_REPLY);
}

void IncomingConnection::get_metadata(const vector<string> &comm) {
	if (comm.size() != 2) {
		reply = get_error_message(201);
	} else if (!server) {
		reply = get_error_message(199);
	} else {
		const File *f = server->get_index().get_file_by_md5(comm[1]);
		if (!f) {
			reply = get_error_message(202);
		} else {
			reply = get_error_message(0);
			reply += f->get_name() + separator;
			reply += int_to_string(f->get_size()) + separator;
			const vector<Fragment *> &fragments = f->get_fragments();
			for (size_t i = 0; i < fragments.size(); i++) {
				reply += fragments[i]->get_md5() + separator;
			}
			reply += separator;
		}
	}
	change_state(SENDING_REPLY);
}

void IncomingConnection::get_sources(const vector<string> &comm) {
	if (comm.size() != 3) {
		reply = get_error_message(201);
	} else if (!server) {
		reply = get_error_message(199);
	} else {
		const File *f = server->get_index().get_file_by_md5(comm[1]);
		if (!f) {
			reply = get_error_message(202);
		} else {
			const Fragment *fragment = f->get_fragment(string_to_int(comm[2]));
			if (!fragment) {
				// TODO: This error message should signal incorrect fragment.
				reply = get_error_message(203);
			} else {
				reply = get_error_message(0);
				const vector<const Source *> &sources =
					fragment->get_sources();
				for (size_t i = 0; i < sources.size(); i++) {
					if (sources[i]->is_address_valid()) {
						reply += sources[i]->get_address() + separator;
					}
				}
				reply += separator;
			}
		}
	}
	change_state(SENDING_REPLY);
}

void IncomingConnection::new_fragment(const vector<string> &comm) {
	if (comm.size() != 3) {
		reply = get_error_message(201);
	} else if (!server) {
		reply = get_error_message(199);
	} else {
		const File *f = server->get_index().get_file_by_md5(comm[1]);
		if (!f) {
			if (server->retrieve_file_info(comm[1], this)) {
				reply = get_error_message(0);
			} else {
				reply = get_error_message(199);
			}
		} else {
			Fragment *fragment = f->get_fragment(string_to_int(comm[2]));
			if (!fragment) {
				// TODO: This error message should signal incorrect fragment.
				reply = get_error_message(203);
			} else {
				reply = get_error_message(0);
				fragment->add_source(this);
			}
		}
	}
	change_state(SENDING_REPLY);
}

void IncomingConnection::change_state(FSMState newstate) {
	state = newstate;
	if (newstate == AWAITING_MESSAGE) filter_events(POLLIN);
	if (newstate == SENDING_REPLY) filter_events(POLLOUT);
}

IncomingConnection::~IncomingConnection() {
	const vector<const File *> &files = server->get_index().get_files();
	for (size_t i = 0; i < files.size(); i++) {
		const vector<Fragment *> &fragments = files[i]->get_fragments();
		for (size_t j = 0; j < fragments.size(); j++) {
			fragments[j]->remove_source(this);
		}
	}
}
